package org.papillion39.sechelper.business;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.SetJoin;

import org.papillion39.sechelper.business.model.Address;
import org.papillion39.sechelper.business.model.Member;
import org.papillion39.sechelper.data.model.Membership;
import org.papillion39.sechelper.data.model.Membership_;
import org.papillion39.sechelper.data.model.Person;
import org.papillion39.sechelper.data.model.PersonAddress;
import org.papillion39.sechelper.data.model.Person_;

/**
 *
 * @author Jonathan Paz <jonathan.paz@ottr.com>
 */
@Stateless
@DeclareRoles({"user","secretary","distributor","admin"})
@RolesAllowed("secretary")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class MembershipManagement implements MembershipManagementLocal {
    @PersistenceContext
    private EntityManager em;

    private Member personToMember(Person p) {
        LinkedList<Address> addresses = new LinkedList<>();
        p.getPersonAddressSet().stream().forEach((address) -> {
            addresses.add(new Address(
                    address.getId(),
                    address.getAddressType(),
                    address.getLine1(),
                    address.getLine2(),
                    address.getLine3(),
                    address.getCity(),
                    address.getStateProvince(),
                    address.getPostalCode(),
                    address.getCounty(),
                    address.getCountry(),
                    address.getDateEffective().toLocalDate(),
                    address.getPreferred()));
        });
        return new Member(p.getId(),
                p.getFirstName(),
                p.getMiddleName(),
                p.getLastName(),
                p.getDateOfBirth().toLocalDate(),
                p.getDateOfDeath().toLocalDate(),
                p.getEmail(),
                p.getPreferEmail(),
                addresses.toArray(new Address[addresses.size()]));
    }

    @Override
    public Member managePerson(int id) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
        Root<Person> person = cq.from(Person.class);
        cq.select(person).where(cb.equal(person.get(Person_.id), id));
        TypedQuery<Person> q = em.createQuery(cq);
        return personToMember(q.getSingleResult());
    }

    public void saveMember(Member m) {
        Person p = em.find(Person.class, m.getId());
        if (p == null) {
            p = new Person();
        }
        p.setFirstName(m.getFirstName());
        p.setMiddleName(m.getMiddleName());
        p.setLastName(m.getLastName());
        p.setDateOfBirth(java.sql.Date.valueOf(m.getDateOfBirth()));
        p.setDateOfDeath(java.sql.Date.valueOf(m.getDateOfDeath()));
        p.setEmail(m.getEmailAddress());
        p.setPreferEmail(m.isPreferEmail());
        Set<PersonAddress> addresses = p.getPersonAddressSet();
        Address[] addrs = m.getAddress();
        boolean[] found = new boolean[addrs.length];
        for (Iterator<PersonAddress> it = addresses.iterator(); it.hasNext();) {
            PersonAddress address = it.next();
            Address addr = null;
            for (int i = 0; i < addrs.length; ++i) {
                if (addrs[i].getId() == address.getId()) {
                    addr = addrs[i];
                    found[i] = true;
                    break;
                }
            }
            if (addr == null) {
                it.remove();
            } else {
                address.setAddressType(addr.getAddressType());
                address.setLine1(addr.getAddressLine1());
                address.setLine2(addr.getAddressLine2());
                address.setLine3(addr.getAddressLine3());
                address.setCity(addr.getCity());
                address.setStateProvince(addr.getState());
                address.setPostalCode(addr.getZipCode());
                address.setCounty(addr.getCounty());
                address.setCountry(addr.getCountry());
                address.setDateEffective(java.sql.Date.valueOf(addr.getAddressEffective()));
                address.setPreferred(addr.isPreferred());
            }
        }
        for (int i = 0; i < found.length; ++i) {
            if (!found[i]) {
                PersonAddress address = new PersonAddress();
                Address addr = addrs[i];
                address.setAddressType(addr.getAddressType());
                address.setLine1(addr.getAddressLine1());
                address.setLine2(addr.getAddressLine2());
                address.setLine3(addr.getAddressLine3());
                address.setCity(addr.getCity());
                address.setStateProvince(addr.getState());
                address.setPostalCode(addr.getZipCode());
                address.setCounty(addr.getCounty());
                address.setCountry(addr.getCountry());
                address.setDateEffective(java.sql.Date.valueOf(addr.getAddressEffective()));
                address.setPreferred(addr.isPreferred());
                addresses.add(address);
            }
        }
        em.merge(p);
    }

    @Override
    public List<Member> roster(int organizationId) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
        Root<Person> person = cq.from(Person.class);
        SetJoin<Person,Membership> membership = person.join(Person_.membershipSet);
        cq.select(person).where(cb.equal(membership.get(Membership_.organizationId), organizationId));
        TypedQuery<Person> q = em.createQuery(cq);
        List<Person> persons = q.getResultList();
        ArrayList<Member> members = new ArrayList<>(persons.size());
        persons.stream().forEach(p -> {
            members.add(personToMember(p));
        });
        return members;
    }

    @Override
    public void saveMembers(List<Member> members) {
        members.stream().forEach((Member m) -> {
            saveMember(m);
        });
    }
}
